/*
    日志落地模块的实现
        1. 抽象落地基类
        2. 派生子类（根据不同的落地方向进行派生）
        3. 使用工厂模式进行创建与表示分离
*/

#pragma once
#include "Util.hpp"
#include <sstream>
#include <cassert>
#include <fstream>
#include <memory>

namespace XKL
{
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>;
        virtual ~LogSink() {}
        virtual void Log(const char *data, size_t len) = 0;
    };
    // 标准输出方向
    class StdoutSink : public LogSink
    {
    public:
        void Log(const char *data, size_t len)
        {
            std::cout.write(data, len);
        }
    };
    // 指定文件方向
    class FileSink : public LogSink
    {
    public:
        // 构造时传入文件名，并打开文件，将操作句柄管理起来
        FileSink(const std::string &filename)
            : _filename(filename)
        {
            Util::File::CreateDirectory(Util::File::Path(filename));
            _ofs.open(filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }

        void Log(const char *data, size_t len)
        {
            _ofs.write(data, len);
            assert(_ofs.good());
        }

    private:
        std::string _filename;
        std::ofstream _ofs;
    };
    // 滚动文件方向（文件大小控制）
    class RollSink : public LogSink
    {
    public:
        RollSink(const std::string &basename, size_t max_fsize)
            : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0), count(0)
        {
            std::string filename = CreateFileName();
            Util::File::CreateDirectory(Util::File::Path(filename));
            _ofs.open(filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open());
        }
        // 进行文件大小判断，超过指定大小就重新创建文件
        void Log(const char *data, size_t len)
        {
            if (_cur_fsize >= _max_fsize)
            {
                _ofs.close();
                _cur_fsize = 0;
                _ofs.open(CreateFileName(), std::ios::binary | std::ios::app);
                assert(_ofs.is_open());
            }

            _ofs.write(data, len);
            _cur_fsize += len;
            assert(_ofs.good());
        }

    private:
        std::string CreateFileName()
        {
            time_t ctime = Util::Date::Current();
            struct tm cur;
            localtime_r(&ctime, &cur);

            std::stringstream filename;
            filename << _basename;
            filename << cur.tm_year + 1900;
            filename << cur.tm_mon + 1;
            filename << cur.tm_mday;
            filename << cur.tm_hour;
            filename << cur.tm_min;
            filename << cur.tm_sec;
            filename << "-";
            filename << count++;
            filename << ".log";

            return filename.str();
        }

    private:
        // 通过基础文件名 + 扩展文件名（以时间生成），组成实际的当前输出文件名
        size_t count; // 如果一秒钟内输出过快会导致创建文件名相同
        std::string _basename;
        std::ofstream _ofs;
        size_t _max_fsize;
        size_t _cur_fsize;
    };

    class SinkFactory
    {
    public:
        template <class SinkType, class... Args>
        static LogSink::ptr Create(Args &&...args)
        {
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };
}